//=========================================================================
// Copyright (C) 2024 The C++ Component Model(COMO) Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//=========================================================================

// REF: https://github.com/AndersKaloer/Ring-Buffer

/**
 * A simple ring buffer (circular buffer) designed for embedded systems.
 *
 * The size of the memory buffer must be a power-of-two, and the ring buffer can
 * contain at most `buf_size-1` bytes.

 * A new ring buffer is created using the `ring_buffer_init(&ring_buffer, buff,
 * sizeof(buff))` function.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#ifdef SYNC_WITH_PHXPAXOS
  #include "PhxPaxosC.h"
#endif
#include "voter_logger.h"

static char         g_buffer[VOTER_LOGGERS_SIZE];

// 在跨平台或不确定编译器支持的情况下，显式调用初始化函数是最可靠的方法
static VoterLogger  g_voterLogger;
static VoterLogger *g_pVoterLogger = &g_voterLogger;

static VoterRunningState  g_voterRunningState;
VoterRunningState *g_pVoterRunningState = &g_voterRunningState;

/**
 * Implementation of ring buffer functions.
 */
int ring_buffer_init(VoterLogger *vloger, char *buf, int buf_size)
{
    if (RING_BUFFER_IS_POWER_OF_TWO(buf_size) != 1) {
        return -1;
    }

    vloger->buffer = buf;
    vloger->buffer_mask = buf_size - 1;
    vloger->tail_index = 0;
    vloger->head_index = 0;

    return 0;
}

/**
 * ring_buffer_queue
 */
void ring_buffer_queue(VoterLogger *vloger, char data)
{
    // Is buffer full?
    if (ring_buffer_is_full(vloger)) {
        // Is going to overwrite the oldest byte
        // Increase tail index
        vloger->tail_index = ((vloger->tail_index + 1) & RING_BUFFER_MASK(vloger));
    }

    // Place data in buffer
    vloger->buffer[vloger->head_index] = data;
    vloger->head_index = ((vloger->head_index + 1) & RING_BUFFER_MASK(vloger));
}

/**
 * ring_buffer_queue_arr
 */
void ring_buffer_queue_arr(VoterLogger *vloger, const char *data, int size)
{
    // Add bytes; one by one
    for (int i = 0;  i < size;  i++) {
        ring_buffer_queue(vloger, data[i]);
    }
}

/**
 * ring_buffer_dequeue
 */
int ring_buffer_dequeue(VoterLogger *vloger, char *data)
{
    if (ring_buffer_is_empty(vloger)) {
        return 0;
    }

    *data = vloger->buffer[vloger->tail_index];
    vloger->tail_index = ((vloger->tail_index + 1) & RING_BUFFER_MASK(vloger));
    return 1;
}

/**
 * ring_buffer_dequeue_arr
 */
int ring_buffer_dequeue_arr(VoterLogger *vloger, char *data, int len)
{
    if (ring_buffer_is_empty(vloger)) {
        // No items
        return 0;
    }

    char *data_ptr = data;
    int cnt = 0;
    while ((cnt < len) && ring_buffer_dequeue(vloger, data_ptr)) {
        cnt++;
        data_ptr++;
    }
    return cnt;
}

/**
 * ring_buffer_peek
 */
int ring_buffer_peek(VoterLogger *vloger, char *data, int index)
{
    if (index >= ring_buffer_num_items(vloger)) {
        // No items at index
        return 0;
    }

    // Add index to pointer
    int data_index = ((vloger->tail_index + index) & RING_BUFFER_MASK(vloger));
    *data = vloger->buffer[data_index];
    return 1;
}

extern inline bool ring_buffer_is_empty(VoterLogger *vloger);
extern inline bool ring_buffer_is_full(VoterLogger *vloger);
extern inline int ring_buffer_num_items(VoterLogger *vloger);

/**
 * InitLog
 */
int InitLog(void)
{
#ifdef SYNC_WITH_PHXPAXOS
    const char *logLevel = getenv("COMO_LOGLEVEL");
    if (NULL == logLevel) {
        PhxSetLogLevel(-1);
    }
    else {
        PhxSetLogLevel(atoi(logLevel));
    }

    const char *logKeywords = getenv("COMO_LOGKEYWORDS");
    if (NULL != logKeywords) {
        PhxSetLogKeywords(logKeywords);
    }
#endif

    memset(g_pVoterRunningState, '\0', sizeof(VoterRunningState));

    return ring_buffer_init(g_pVoterLogger, g_buffer, VOTER_LOGGERS_SIZE);
}

/**
 * WriteLog
 */
int32_t WriteLog(char *buf, int32_t dataLen)
{
    if (NULL == buf) {
        return -1;
    }

    if (dataLen < 0) {
        dataLen = strlen(buf) + 1;
    }

    // 如果空间不够，就不给写，否则会造成事务完整性出问题
    if ((ring_buffer_num_items(g_pVoterLogger) + dataLen +
                      (int32_t)sizeof(int32_t)) < g_pVoterLogger->buffer_mask) {
        return -2;
    }

    // 先写入数据长度
    ring_buffer_queue_arr(g_pVoterLogger, (char *)(&dataLen), sizeof(int32_t));
    ring_buffer_queue_arr(g_pVoterLogger, buf, dataLen);

    return 0;
}

/**
 * ReadLog
 */
char *ReadLog(char *buf, int32_t bufSize)
{
    int32_t dataLen;

    // 先取出数据长度
    ring_buffer_dequeue_arr(g_pVoterLogger, (char *)(&dataLen), sizeof(int32_t));

    // 如果空间不够，返回 NULL
    if (dataLen > bufSize) {
        return NULL;
    }

    ring_buffer_dequeue_arr(g_pVoterLogger, buf, dataLen);

    return buf;
}
